﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="AttachedCollection.cs" company="JDML Consulting Ltd.">
//   Copyright 2011 Photon Project - http://photon.codeplex.com
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Photon.Windows
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Linq;
    using System.Windows;
    using System.Windows.Interactivity;
    using Collections;

    /// <summary>
    /// A collection of <see cref="IAttachedObject"/>.
    /// </summary>
    /// <typeparam name="TItem">The type of the item.</typeparam>
    public class AttachedCollection<TItem> : DependencyObjectCollection<TItem>, 
        IAttachedObject where TItem : DependencyObject, IAttachedObject 
    {
        #region Fields

        private List<TItem> _attachedItems = new List<TItem>();

        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="AttachedCollection&lt;TItem&gt;"/> class.
        /// </summary>
        public AttachedCollection()
        {
            ((INotifyCollectionChanged)this).CollectionChanged += HandleCollectionChanged;
        }

        #region IAttachedObject Members

        void IAttachedObject.Attach(DependencyObject associatedObject)
        {
            WritePreamble();
            if (AssociatedObject != associatedObject)
            {
                if (AssociatedObject != null)
                {
                    throw new InvalidOperationException();
                }

                AssociatedObject = associatedObject;
                OnAttached();
            }
            WritePostscript();
        }

        void IAttachedObject.Detach()
        {
            WritePreamble();
            if (AssociatedObject != null)
            {
                OnDetaching();
                AssociatedObject = null;
            }
            WritePostscript();
        }

        public DependencyObject AssociatedObject { get; private set; }

        #endregion

        protected virtual void HandleCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                case NotifyCollectionChangedAction.Remove:
                case NotifyCollectionChangedAction.Replace:
                    if (e.OldItems != null)
                    {
                        e.OldItems.Cast<TItem>().ForEach(OnItemRemoved);
                    }

                    if (e.NewItems != null)
                    {
                        e.NewItems.Cast<TItem>().ForEach(OnItemAdded);
                    }

                    break;
                case NotifyCollectionChangedAction.Reset:
                    if (Count != 0)
                    {
                        throw new NotSupportedException(
                            "Only reset events raised during clear operations are currently supported.");
                    }

                    OnCleared();
                    break;
            }
        }

        private void OnCleared()
        {
            if (_attachedItems != null)
            {
                foreach (var item in _attachedItems.Distinct())
                {
                    DetachItem(item);
                }

                _attachedItems = null;
            }
        }

        private void OnItemRemoved(TItem item)
        {
            // locate first instance of the item
            var index = _attachedItems.IndexOf(item);

            // remove the instance from the list of attached items
            _attachedItems.RemoveAt(index);
            if (index >= _attachedItems.Count || _attachedItems.IndexOf(item, index) == -1)
            {
                DetachItem(item);
            }

            // cleanup
            if (_attachedItems.Count == 0)
            {
                _attachedItems = null;
            }
        }

        protected void OnItemAdded(TItem item)
        {
            if (!IsAttached(item))
            {
                AttachItem(item);
            }

            if (_attachedItems == null)
            {
                _attachedItems = new List<TItem>();
            }

            _attachedItems.Add(item);
        }

        private void OnAttached()
        {
            this.ForEach(AttachItem);
        }

        private void OnDetaching()
        {
            this.ForEach(DetachItem);
        }

        private void DetachItem(TItem item)
        {
            item.Detach();
            OnItemDetached(item);
        }

        protected virtual void OnItemDetached(TItem item)
        {
            if (ItemDetached != null)
            {
                ItemDetached(this, new AttachedItemEventArgs<TItem>(item));
            }
        }

        protected virtual void OnItemAttached(TItem item)
        {
            if (ItemAttached != null)
            {
                ItemAttached(this, new AttachedItemEventArgs<TItem>(item));
            }
        }

        public event EventHandler<AttachedItemEventArgs<TItem>> ItemAttached;

        public event EventHandler<AttachedItemEventArgs<TItem>> ItemDetached;

        private void AttachItem(TItem item)
        {
            if (AssociatedObject != null)
            {
                item.Attach(AssociatedObject);
                OnItemAttached(item);
            }
        }

        private bool IsAttached(TItem item)
        {
            return _attachedItems != null && _attachedItems.Contains(item);
        }
    }

    public class AttachedItemEventArgs<T> : EventArgs
    {
        public AttachedItemEventArgs(T item)
        {
            Item = item;
        }

        public T Item { get; private set; }
    }
}